/* @flow */

// https://github.com/Hanks10100/weex-native-directive/tree/master/component

import { mergeOptions, isPlainObject, noop } from "core/util/index";
import Watcher from "core/observer/watcher";
import { initProxy } from "core/instance/proxy";
import { initState, getData } from "core/instance/state";
import { initRender } from "core/instance/render";
import { initEvents } from "core/instance/events";
import { initProvide, initInjections } from "core/instance/inject";
import { initLifecycle, callHook } from "core/instance/lifecycle";
import {
  initInternalComponent,
  resolveConstructorOptions,
} from "core/instance/init";
import { registerComponentHook, updateComponentData } from "../../util/index";

let uid = 0;

// override Vue.prototype._init
function initVirtualComponent(options: Object = {}) {
  const vm: Component = this;
  const componentId = options.componentId;

  // virtual component uid
  vm._uid = `virtual-component-${uid++}`;

  // a flag to avoid this being observed
  vm._isVue = true;
  // merge options
  if (options && options._isComponent) {
    // optimize internal component instantiation
    // since dynamic options merging is pretty slow, and none of the
    // internal component options needs special treatment.
    initInternalComponent(vm, options);
  } else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    );
  }

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== "production") {
    initProxy(vm);
  } else {
    vm._renderProxy = vm;
  }

  vm._self = vm;
  initLifecycle(vm);
  initEvents(vm);
  initRender(vm);
  callHook(vm, "beforeCreate");
  initInjections(vm); // resolve injections before data/props
  initState(vm);
  initProvide(vm); // resolve provide after data/props
  callHook(vm, "created");

  // send initial data to native
  const data = vm.$options.data;
  const params = typeof data === "function" ? getData(data, vm) : data || {};
  if (isPlainObject(params)) {
    updateComponentData(componentId, params);
  }

  registerComponentHook(componentId, "lifecycle", "attach", () => {
    callHook(vm, "beforeMount");

    const updateComponent = () => {
      vm._update(vm._vnode, false);
    };
    new Watcher(vm, updateComponent, noop, null, true);

    vm._isMounted = true;
    callHook(vm, "mounted");
  });

  registerComponentHook(componentId, "lifecycle", "detach", () => {
    vm.$destroy();
  });
}

// override Vue.prototype._update
function updateVirtualComponent(vnode?: VNode) {
  const vm: Component = this;
  const componentId = vm.$options.componentId;
  if (vm._isMounted) {
    callHook(vm, "beforeUpdate");
  }
  vm._vnode = vnode;
  if (vm._isMounted && componentId) {
    // TODO: data should be filtered and without bindings
    const data = Object.assign({}, vm._data);
    updateComponentData(componentId, data, () => {
      callHook(vm, "updated");
    });
  }
}

// listening on native callback
export function resolveVirtualComponent(vnode: MountedComponentVNode): VNode {
  const BaseCtor = vnode.componentOptions.Ctor;
  const VirtualComponent = BaseCtor.extend({});
  const cid = VirtualComponent.cid;
  VirtualComponent.prototype._init = initVirtualComponent;
  VirtualComponent.prototype._update = updateVirtualComponent;

  vnode.componentOptions.Ctor = BaseCtor.extend({
    beforeCreate() {
      // const vm: Component = this

      // TODO: listen on all events and dispatch them to the
      // corresponding virtual components according to the componentId.
      // vm._virtualComponents = {}
      const createVirtualComponent = (componentId, propsData) => {
        // create virtual component
        // const subVm =
        new VirtualComponent({
          componentId,
          propsData,
        });
        // if (vm._virtualComponents) {
        //   vm._virtualComponents[componentId] = subVm
        // }
      };

      registerComponentHook(cid, "lifecycle", "create", createVirtualComponent);
    },
    beforeDestroy() {
      delete this._virtualComponents;
    },
  });
}
